Rekurzija - ponovitev


SSCČ se vrača

Že pri prejšnjih nalogah smo spoznali SSČS. Ponovimo še enkrat - seznam seznamov celih števil (SSCŠ) je seznam, katerega elementi so bodisi cela števila bodisi seznami seznamov celih števil. Da bo enostavneje, recimo, da je tudi prazen seznam SSCŠ. Nekaj primerov:

      [-1, 2, -3]
      [1, [2], 3, [2, 3, 4]]
      [[[[0]], 1], [2, [[-3], [4]]]]

1. podnaloga

Sestavi funkcijo ločiStevila(sscs), ki vrne trojico (poz, nic, neg), ki pove koliko je v sscs pozitivnih, ničelnih in koliko negativnih celih števil. Za primere od zgoraj so rezultati:

      (1, 0, 2)
      (6, 0, 0)
      (3, 1, 1)

Predpostavi, da je parameter zagotovo SSCS,

Uradna rešitev

def ločiStevila(sscs):
    '''Razdeli podatke v SSCS na pozitivne, ničelne in negativne'''
    if len(sscs) == 0 : # tudi prazen seznam je SSCS
        return (0, 0, 0) # in nima celih števil
    kolikoPozStevil = 0
    kolikoNegStevil = 0
    kolikoNic = 0
    # pregledamo vse elemente
    for el in sscs :
        # če je element 'običajno število'
        if isinstance(el, int):
            if el > 0:
                kolikoPozStevil += 1
            elif el < 0:
                kolikoNegStevil += 1
            else:
                kolikoNic += 1
        else : # potem mora pa biti sscs in moramo prešteti vsa cela števila v njem
            trojka = ločiStevila(el)
            kolikoPozStevil, kolikoNic, kolikoNegStevil = kolikoPozStevil + trojka[0], \
                                                          kolikoNic  + trojka[1], kolikoNegStevil + trojka[2]
    return kolikoPozStevil, kolikoNic, kolikoNegStevil

2. podnaloga

Sestavi funkcijo jeSSCS(sscs), ki preveri (vrne True oz. False), če je dan seznam res seznam seznamov celih števil.

POZOR: Python pravi, da je isinstance(True, int) enako True, pa tudi isinstance(False, int) enako True, zato se "znebi" napačnih logičnih vrednosti z isinstance(True, bool)

Uradna rešitev

def jeSSCS(sscs):
    '''Ali je sscs res seznam seznamov celih števil'''
    if not isinstance(sscs, list): # če sploh ne gre za seznam
        return False
    if len(sscs) == 0 : # tudi prazen seznam je SSCS
        return True 
    # pregledamo vse elemente
    for el in sscs :
        # če je element logična vrednost
        if isinstance(el, bool):
            return False
        # če element ni ne SSCS ne celo število
        if not isinstance(el, int) and  \
           not jeSSCS(el) : # ne SSCE
            return False
    # vsi testi OK       

    return True

3. podnaloga

Sestavi funkcijo kolikoNapacnih(sscs), ki prešteje, koliko je v seznamu seznamov celih števil napačnih podatkov, torej elementov, ki niso ne cela števila, ne SSCŠ. Če argument ni seznam, vrni None

    >>>kolikoNapacnih([1, 'bla', 2])
    1
    >>>kolikoNapacnih([1, [['bla', 'blu'], False], 2])
    1
    >>>kolikoNapacnih([[1.8], [2, 3, [4]], [False, 4, [True]]])
    2
    >>>kolikoNapacnih(12)
    None

Uradna rešitev

def kolikoNapacnih(sscs):
    '''Koliko elementov je v sscs napačnih'''
    if not isinstance(sscs, list):
        return None
    kolikoNarobe = 0
    # pregledamo vse elemente
    for el in sscs:
        if (not isinstance(el, int)) and (not jeSSCS(el)) \
                or isinstance(el, bool):
                kolikoNarobe += 1
    return kolikoNarobe

Permutacije

Oznake idej se nanašajo na prosojnice permutacije v spletni učilnici FMF za predmet Programiranje 1 v š.l. 15/16

Če hočemo narediti permutacijo iz 0 elementov, vrnemo tako kot itertools [[]]

1. podnaloga

Na osnovi ideje 1 smo zgradili osnutek metode permutacijeV1(seznam). Dopolni ga do delujočega!

    def permutacijeV1(seznam):
       '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
           permutacijo elementov iz seznama '''
       ... manjka
       prviDel = seznam[:-1]
       zadnji = seznam[-1]
       permPrej = permutacijeV1(prviDel)
       # vstavi zadnji na vsa možna mesta
       rez = []
       for
          koncnaPer = permPrej z vstavljenim zadnjim
          rez.append(koncnaPer)
       ...
       return sorted(rez)

Uradna rešitev

def permutacijeV1(seznam):
    '''Vrne seznam seznamov, kjer posamezni seznam predstavlj
    permutacijo elementov iz seznama '''
    if seznam == []: # ni elementov, le prazna permutacija
        return [[]]
    if len(seznam) == 1: # en sam element - ena permutacija!
        return [[seznam[0]]]
    prviDel = seznam[:-1]
    zadnji = seznam[-1]
    permPrej = permutacijeV1(prviDel) 
    rez = []
    for posameznaP in permPrej:
        '''Iz te perm. naredimo len(seznam) novih '''
        for i in range(len(seznam)):
            novaP = posameznaP[:]
            novaP.insert(i, zadnji)
            rez.append(novaP)
    return sorted(rez)

2. podnaloga

Na osnovi ideje 2 smo zgradili osnutek metode permutacijeV2(seznam). Dopolni ga do delujočega!

    def permutacijeV2(seznam):
       '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
           permutacijo elementov iz seznama '''
       ... manjka
       vse = []
       for ind, elt in enumerate(seznam):
          # zgradimo vse permutacije iz ostalih
          vsePerm = permutacijeV2(seznam[:ind] + seznam[ind + 1:] )
          # in sedaj permutacijam dodamo na začetek element elt
          ...
       ...
       return sorted(vse)

Uradna rešitev

def permutacijeV2(seznam):
    '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
        permutacijo elementov iz seznama '''
    if seznam == []: # ni elementov, le prazna permutacija
        return [[]] 
    vse = []
    for ind, elt in enumerate(seznam):
       # zgradimo vse permutacije iz ostalih
       vsePerm = permutacijeV2(seznam[:ind] + seznam[ind + 1:] )
       # in sedaj permutacijam dodamo na začetek element elt
       for perm in vsePerm:
           vse.append([elt] + perm)
    return sorted(vse)

3. podnaloga

Na osnovi ideje 3 smo zgradili osnutek metode permutacijeV3(seznam). Dopolni ga do delujočega!

    def permutacijeV3(seznam, doSedaj = []):
       '''Vrne seznam vseh možnih permutacij iz elementov v elementi,
          ki imajo začetek enak permutaciji, ki so predstava doSedaj'''
       # Če smo že na koncu
           return [doSedaj]
       vse = []
       for elt in seznam:
          # če elt še lahko dodamo v doslej sestavljeno permutacijo
               # generiramo vse permutacije z začetkom doSedaj + [elt]
               # dodamo vsem
       return sorted(vse)

Uradna rešitev

def permutacijeV3(seznam, doSedaj = []):
    '''Vrne seznam vseh možnih permutacij iz elementov v elementi,
       ki imajo začetek enak permutaciji, ki so predstava doSedaj'''
    if seznam == []: # ni elementov, le prazna permutacija
        return [[]]
    if len(seznam) == len(doSedaj):# Če smo že na koncu
        return [doSedaj]
    vse = []
    for elt in seznam:
       if not elt in doSedaj: # če elt še lahko dodamo v doslej sestavljeno permutacijo
            permut = permutacijeV3(seznam, doSedaj + [elt]) # generiramo vse permutacije z začetkom doSedaj + [elt]
            vse += permut #dodamo vsem
    return sorted(vse)

4. podnaloga

Na osnovi ideje 4 smo zgradili osnutek metode permutacijeV4(seznam). Dopolni ga do delujočega!

    import itertools
    def permutacijeV4(seznam):
       '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
           permutacijo elementov iz seznama '''
       # uporabimo itertools
       # pazimo, ker ta vrne seznam naborov!
       return sorted(vse)

Uradna rešitev

import itertools
def permutacijeV4(seznam):        
    '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
        permutacijo elementov iz seznama '''
    # uporabimo itertools
    permu = itertools.permutations(seznam)
    vse = list(map(list, permu)) # pazimo, ker ta vrne seznam naborov!
    return sorted(vse)

Briši ...

1. podnaloga

Sestavi funkcijo izbrisiPrazneMape(potDoMape), ki v izbrani mapi (in njenih podmapah) pobriše vse prazne datoteke (tiste z velikostjo 0) in vse prazne mape (tiste, ki ne vsebujejo nobenih datotek ali podmap). Funkcija naj vrne par, ki ga sestavljata število pobrisanih datotek in število pobrisanih map, oziroma None, če potDoMape ne opisuje imenika Pozor: ko zbrišemo prazno datoteko, se lahko sprazni tudi mapa.

Pred testiranjem vedno skopirajte to datoteko ZIP (staro mapo PythonOsRek pobrišite!) in jo razširite (nastala bo mapa PythonOsRek s podmapami testX in EURO2012)

Uradna rešitev

import os

def izbrisiPrazneMape(potDoMape):
    '''Izbriši vse prazne datoteke in prazne mape'''
    kolikoDat, kolikoMap = 0, 0
    if not os.path.isdir(potDoMape): # ni mapa
        return None
    vseDat = os.listdir(potDoMape)
    for datoteka in vseDat:
        #izbrisemo samo prazne datoteke
        if os.path.isfile(potDoMape + "\\" + datoteka) and os.path.getsize(potDoMape + "\\" + datoteka) == 0:
            os.remove(potDoMape + "\\" + datoteka)
            kolikoDat += 1
        elif os.path.isdir(potDoMape + "\\" + datoteka): #filtriramo mape
            kolDat, kolMap = izbrisiPrazneMape(potDoMape + "\\" + datoteka)
            kolikoDat += kolDat
            kolikoMap += kolMap
    #če je mapa sedaj prazna, jo izbrisemo
    if len(os.listdir(potDoMape)) == 0:
        os.rmdir(potDoMape)
        kolikoMap += 1
    return kolikoDat, kolikoMap

Collatzovo zaporedje

Collatzovo zaporedje tvorimo na sledeč način. Začnemo z nekim naravnim številom $n$, ki ga nato delimo z $2$, če je sodo, ali pa pomnožimo s $3$ in prištejemo $1$, če je liho. Postopek ponavljamo, dokler ne pridemo do števila $1$ (v tem primeru stvar ni več zanimiva, saj se začno ponavljati števila $1, 4, 2, 1, 4, 2, 1, \ldots$). Primer zaporedja, ki se začne z $6$ je tako $6, 3, 10, 5, 16, 8, 4, 2, 1$. Collatzova domneva, ki trdi, da za poljubno naravno število njegovo Collatzovo zaporedje sčasoma doseže $1$, je še vedno nerešena.

Vse naloge, razen prve, rešite z rekurzijo!

1. podnaloga

Sestavite funkcijo naslednji_clen(n), ki izračuna člen, ki v Collatzovemu zaporedju sledi številu n.

Uradna rešitev

def naslednji_clen(n):
    '''člen, ki v Collatzovemu zaporedju sledi številu `n`'''
    if n % 2 == 0:
        return n // 2
    else:
        return 3 * n + 1

2. podnaloga

Sestavite funkcijo dolzina_zaporedja(n), ki izračuna dolžino Collatzovega zaporedja, ki se začne s številom n.

Uradna rešitev

def dolzina_zaporedja(n):
    '''dolžina Collatzovega zaporedja, ki se začne s številom `n`.'''
    if n == 1:
        return 1
    else:
        return 1 + dolzina_zaporedja(naslednji_clen(n))

3. podnaloga

Sestavite funkcijo najvecji_clen(n), ki izračuna največji člen v Collatzovem zaporedju, ki se začne s številom n.

Uradna rešitev

def najvecji_clen(n):
    '''največji člen v Collatzovem zaporedju, ki se začne s številom `n`.'''
    if n == 1:
        return 1
    else:
        return max(n, najvecji_clen(naslednji_clen(n)))

4. podnaloga

Sestavite funkcijo najdaljse_zaporedje(m, n), ki vrne dolžino najdaljšega zaporedja med vsemi tistimi Collatzovimi zaporedji, ki se začnejo s števili med (vključno) m in n.

Uradna rešitev

def najdaljse_zaporedje(m, n):
    '''Dolžina najdaljšega zaporedja med vsemi tistimi Collatzovimi zaporedji,
       ki se začnejo s števili med (vključno) m in n.'''
    if m == n:
        return dolzina_zaporedja(m)
    else:
        return max(dolzina_zaporedja(m), najdaljse_zaporedje(m + 1, n))